#include "qe_log.h"
#include "qe_serial.h"
#include "qe_memory.h"
#include "qe_assert.h"
#include "qe_buffer.h"



QELOG_DOMAIN("qe-serial");



static qe_int serial_poll_rx(qe_serial_dev *serial, qe_u8 *data, qe_uint length)
{
	qe_int c;
	qe_int size;

	qe_assert(serial != QE_NULL);

	size = length;

	while (length) {
		c = serial->ops->getc(serial);
		if (c == -1) 
			break;
	
		*data++ = c;
		length--;

		if (serial->parent.open_flag & QE_DEV_F_STREAM) {
			if (c == '\n') break;
		}
	}

	return size - length;
}

static inline int serial_int_rx2(qe_serial_dev *serial, qe_u8 *data, int length)
{
	qe_assert(serial != QE_NULL);
	qe_assert(serial->rx_ring != QE_NULL);
	return qe_ringbuffer_read(serial->rx_ring, data, length);
}

/**
 * @brief Serial interrupt mode transmit
 * @param[in] serial: serial instance
 * @param[in] data: transmit data buffer
 * @param[in] length: transmit data length
 * 
 * @details Serial using interrupt mode transmit must implement ops->transfer_start,
 * serial layer copy data into tx ring buffer, and call driver's transfer_start. Driver
 * write data from ring buffer to device's FIFO, and start the transmit. One time transmit
 * write size is the same as device's FIFO size, and wait interrupt to assert next transfer.
 * 
 * @return <0:fail 0:success
 */
static inline qe_int serial_int_tx(qe_serial_dev *serial, const qe_u8 *data, 
	qe_uint length)
{
	qe_uint free;

	qe_assert(serial != QE_NULL);
	qe_assert(data != QE_NULL);
	qe_assert(serial->tx_ring != QE_NULL);
	qe_assert(serial->ops->transfer_start != QE_NULL);

	free = qe_ringbuffer_freesize(serial->tx_ring);
	if (qe_unlikely(free < length)) {
		qe_warning("tx ring overflow, free:%d data:%d", free, length);
	}

	/* write data into */
	qe_ringbuffer_write(serial->tx_ring, (char *)data, length);

	/* start async transfer */
	return serial->ops->transfer_start(serial);
}

static inline int serial_poll_tx(qe_serial_dev *serial, const qe_u8 *data,
	int length)
{
	int size;
	qe_assert(serial != QE_NULL);

	size = length;

	while (length--)
	{
        /*
         * to be polite with serial console add a line feed
         * to the carriage return character
         */
        if (*data == '\n' && (serial->parent.open_flag & QE_DEV_F_STREAM))
        {
			serial->ops->putc(serial, '\r');
		}

		serial->ops->putc(serial, *data);

		++data;
	}

	return size - length;
}

static qe_ret qe_serial_init(qe_dev *dev)
{
	qe_serial_dev *serial;

	qe_assert(dev != QE_NULL);

	serial = (qe_serial_dev *)dev;
	serial->rx      = QE_NULL;
	serial->tx      = QE_NULL;
	serial->rx_ring = QE_NULL;
	serial->tx_ring = QE_NULL;

	if (serial->ops->configure) {
		return serial->ops->configure(serial, &serial->config);
	}

	return qe_ok;
}

static qe_ret qe_serial_open(qe_dev *dev, qe_u16 oflag)
{
	qe_ret ret;
	qe_u16 stream_flag = 0x0;
	qe_u16 int_flag = 0x0;
	qe_serial_dev *serial;

	qe_assert(dev != QE_NULL);

	serial = (qe_serial_dev *)dev;

	/* check device flag with the open flag */
    if ((oflag & QE_DEV_F_INT_RX) && !(dev->flag & QE_DEV_F_INT_RX))
        return qe_err_notsupport;
    if ((oflag & QE_DEV_F_INT_TX) && !(dev->flag & QE_DEV_F_INT_TX)) {
        return qe_err_notsupport;
    }

	/* keep steam flag */
	if ((oflag & QE_DEV_F_STREAM) || (dev->open_flag & QE_DEV_F_STREAM))
		stream_flag = QE_DEV_F_STREAM;

	/* get open flags */
	dev->open_flag = oflag & 0xFF;

	/* initialize the tx/rx ring according to open flag */
	if (oflag & QE_DEV_F_INT_RX) {

		/* add open flag */
		dev->open_flag |= QE_DEV_F_INT_RX;
		
		/* create tx ring */
		if (!serial->rx_ring) {
			serial->rx_ring = qe_ringbuffer_new(serial->config.bufsz);
			qe_assert(serial->rx_ring != QE_NULL);
			/* enable tx interrupt */
			int_flag |= QE_DEV_F_INT_RX;
		}
	} else {
		serial->rx_ring = QE_NULL;
	}

	if (oflag & QE_DEV_F_INT_TX) {
		
		/* add open flag */
		dev->open_flag |= QE_DEV_F_INT_TX;
		
		/* create tx ring */
		if (!serial->tx_ring) {
			serial->tx_ring = qe_ringbuffer_new(serial->config.bufsz);
			qe_assert(serial->tx_ring != QE_NULL);
			/* enable tx interrupt */
			int_flag |= QE_DEV_F_INT_TX;
		}
	} else {
		serial->tx_ring = QE_NULL;
	}

	qe_debug("create ring");

	if (int_flag) {
		ret = serial->ops->ioctl(serial, QE_DEV_IOC_SET_INT, (void *)&int_flag);
		if (ret != qe_ok) {
			return ret;
		}
	}

	/* set stream flag */	
	dev->open_flag |= stream_flag;

	return qe_ok;
}

static qe_ret qe_serial_close(qe_dev *dev)
{
	qe_serial_dev *serial;

	qe_assert(dev != QE_NULL);

	serial = (qe_serial_dev *)dev;

	/* this device has more reference count */
	if (dev->reference > 1)
		return qe_ok;

    if (dev->open_flag & QE_DEV_F_INT_RX) {
        /* configure low level device */
        serial->ops->ioctl(serial, QE_DEV_IOC_CLR_INT, (void*)QE_DEV_F_INT_RX);
        dev->open_flag &= ~QE_DEV_F_INT_RX;
        qe_assert(serial->rx_ring != QE_NULL);
        qe_ringbuffer_destroy(serial->rx_ring);
    }

    serial->ops->ioctl(serial, QE_DEV_IOC_CLOSE, QE_NULL);
    dev->flag &= ~QE_DEV_F_ACTIVATED;
    
	return qe_ok;
}

static qe_size qe_serial_read(qe_dev *dev, qe_offs pos, void *buffer, 
	qe_size size)
{
	qe_serial_dev *serial;

	qe_assert(dev != QE_NULL);
	if (size == 0) return 0;

	(void)pos;

	serial = (qe_serial_dev *)dev;

	if (dev->open_flag & QE_DEV_F_INT_RX) {
		return serial_int_rx2(serial, (qe_u8 *)buffer, size);
	}

	return serial_poll_rx(serial, (qe_u8 *)buffer, size);
}

static qe_size qe_serial_write(qe_dev *dev, qe_offs pos, const void *buffer, 
	qe_size size)
{
	qe_serial_dev *serial;

	(void)pos;

	qe_assert(dev != QE_NULL);
	if (size == 0) return 0;

	serial = (qe_serial_dev *)dev;

	if (dev->open_flag & QE_DEV_F_INT_TX) {
		return serial_int_tx(serial, (const qe_u8 *)buffer, size);
	} else {
		return serial_poll_tx(serial, (const qe_u8 *)buffer, size);
	}

	return 0;
}

static qe_ret qe_serial_ioctl(qe_dev *dev, int cmd, void *args)
{
	qe_ret ret = qe_ok;
	qe_serial_dev *serial;

	qe_assert(dev != QE_NULL);

	serial = (qe_serial_dev *)dev;

	qe_debug("ioctl %d", cmd);

	switch (cmd) {

	case QE_DEV_IOC_SUSPEND:
		dev->flag |= QE_DEV_F_SUSPENDED;
		break;

	case QE_DEV_IOC_RESUME:
		dev->flag &= ~QE_DEV_F_SUSPENDED;
		break;

	case QE_DEV_IOC_CONFIG:
		if (args) {
			qe_serial_configure *pcfg = (qe_serial_configure *)args;
			if ((pcfg->bufsz != serial->config.bufsz) && (serial->parent.reference)) {
				/*can not change buffer size*/
				return qe_err_busy;
			}
			/* set serial configure */
			serial->config = *pcfg;
			qe_debug("reference %d", serial->parent.reference);
			if (serial->parent.reference) {
				/* serial device has been opened, to configure it */
				serial->ops->configure(serial, (qe_serial_configure *) args);
			}
		}
		break;

	default:
		ret = serial->ops->ioctl(serial, cmd, args);
		break;
	}

	return ret;
}

static const qe_dev_ops serial_ops = {
	qe_serial_init,
	qe_serial_open,
	qe_serial_close,
	qe_serial_read,
	qe_serial_write,
	qe_serial_ioctl,
};

qe_ret qe_serial_register(qe_serial_dev *serial, const char *name,
	qe_u32 flag, void *data)
{
	qe_dev *dev;

	dev = &(serial->parent);

	dev->type = QE_DEV_CHAR;
	dev->rx_indicate = QE_NULL;
	dev->tx_complete = QE_NULL;

	dev->ops = &serial_ops;

	dev->priv = data;

	return qe_dev_register(dev, name, (qe_u16)flag);
}
